#ifndef GENOME_HH
#define GENOME_HH

#include <vector>
#include <list>
#include <memory>
#include <utility>
#include <iosfwd>

#include "gene.hh"

namespace evolution {

    class Genome {
        std::list<NodeGene> node_genes;
        std::vector<ConnectionGene> connection_genes;

        int min_innov = -1;
        int max_innov = -1;

        const int input_nodes;

    public:
        Genome (int input_nodes) : input_nodes(input_nodes) { }

        void add_node (NodeGene const& node_gene) { node_genes.push_back(node_gene); }
        void add_connection (ConnectionGene const& conn_gene) { connection_genes.push_back(conn_gene); }

        int nodes() const;

        std::list<NodeGene> const& get_node_genes() const { return node_genes; }
        std::vector<ConnectionGene> const& get_connection_genes() const { return connection_genes; }

        friend std::ostream& operator<< (std::ostream& out, Genome const&);
        friend std::istream& operator>> (std::istream& in, Genome&);

        static std::shared_ptr<Genome> empty_genome(int initial_nodes, int input_nodes);
        static std::shared_ptr<Genome> random_genome(int initial_nodes, int input_nodes);
        static std::shared_ptr<Genome> clone_genome(std::shared_ptr<Genome> const& other);
        static std::shared_ptr<Genome> copy_genome(std::shared_ptr<Genome> const& other);

        void mutate();
        void mutate_add_node();
        void mutate_add_connection();
        void mutate_weights();

        std::pair<int, int> get_innovation_range() const { return std::make_pair(min_innov, max_innov); }
        bool is_in_range (int innov) const {
            return innov >= min_innov && innov <= max_innov;
        }
        bool has_gene (int innov) const {
            for (auto conn: connection_genes) { 
                if (conn.get_innovation_number() == innov) return true;
            }
            return false;
        }
        ConnectionGene const* get_gene(int innov) {
            for (auto& conn: connection_genes) {
                if (conn.get_innovation_number() == innov) return &conn;
            }
            return nullptr;
        }

    private:
        std::list<NodeGene>::iterator find_node(int id);
        void check_innov(int innov);
    };

}

#endif
